package com.googlecode.afdbaccess.web.common.login;

import java.security.Principal;
import java.security.acl.Group;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.Enumeration;

import org.jboss.security.SimpleGroup;
import org.jboss.security.SimplePrincipal;
import org.junit.Assert;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.BDDMockito;
import org.mockito.BDDMockito.BDDMyOngoingStubbing;
import org.mockito.Matchers;
import org.mockito.Mockito;
import org.mockito.runners.MockitoJUnitRunner;

import com.googlecode.afdbaccess.cache.TransactionBase;
import com.googlecode.afdbaccess.cache.exceptions.DataLayerException;
import com.googlecode.afdbaccess.cache.exceptions.DataLayerWrappedSqlException;
import com.googlecode.afdbaccess.cache.preparedstatement.PreparedStatementArgumentsSetter;
import com.googlecode.afdbaccess.cache.resultconversion.exceptions.DataLayerWrappedResultSetConversionException;
import com.googlecode.afdbaccess.cache.resultconversion.exceptions.NoRowsReturnedException;

@RunWith(MockitoJUnitRunner.class)
@SuppressWarnings("boxing")
public class UserInfoFetcherTest {
    private static final String DISPLAYNAME_VALUE = "theDisplayName";
    private static final long USERID_VALUE = 101L;
    private static final String USERNAME_VALUE = "theUsername";
    
    @Test
    public void testUserNotFound() throws Exception {
        // Given
        ResultSet rsMock = Mockito.mock(ResultSet.class);
        BDDMockito.given(rsMock.next()).willReturn(false);
        
        TransactionBase tMock = mockTransaction(rsMock);
        
        try {
            // When
            new UserInfoFetcher().perform(tMock, USERNAME_VALUE);
        } catch (NoRowsReturnedException nrre) {
            // Then
            // Good
            return;
        }
        Assert.fail("Should not reach this point. Exception should have been thrown.");
    }

    @Test
    public void testConverterWithNoRole() throws Exception {
        String[] groupNames = {};
        testConverter(groupNames);
    }

    @Test
    public void testConverterWithOneRole() throws Exception {
        String[] groupNames = { "rolerow1" };
        testConverter(groupNames);
    }

    @Test
    public void testConverterWithTwoRoles() throws Exception {
        String[] groupNames = { "rolerow1", "rolerow2" };
        testConverter(groupNames);
    }
    
    public void testConverter(final String[] groupNames) throws Exception {
        // Given
        ResultSet rsMock = Mockito.mock(ResultSet.class);
        mockUserInfo(rsMock);
        mockGroups(rsMock, groupNames);
        
        TransactionBase tMock = mockTransaction(rsMock);

        // When
        Group[] result = new UserInfoFetcher().perform(tMock, USERNAME_VALUE);

        // Then
        Assert.assertEquals(2, result.length);
        assertUserInfo(result[1]);
        assertGroups(result[0], groupNames);
    }

    //
    
    @SuppressWarnings("unchecked")
    private TransactionBase mockTransaction(final ResultSet resultSet) throws DataLayerException, DataLayerWrappedSqlException, DataLayerWrappedResultSetConversionException {
        TransactionBase tMock = Mockito.mock(TransactionBase.class);
        BDDMockito.given(tMock.executeQuery(Matchers.anyString(), Matchers.any(PreparedStatementArgumentsSetter.class), Matchers.any(Class.class), Matchers.anyLong()))
        .willAnswer(new ResultFromConverterWithResultAnswer<Group[]>(resultSet));
        return tMock;
    }

    //

    private void mockUserInfo(ResultSet rsMock) throws SQLException {
        BDDMockito.given(rsMock.getString("identification")).willReturn(USERNAME_VALUE);
        BDDMockito.given(rsMock.getLong("Person_id")).willReturn(USERID_VALUE);
        BDDMockito.given(rsMock.getString("displayName")).willReturn(DISPLAYNAME_VALUE);
    }

    private void assertUserInfo(Group result) {
        ApplicationPrincipal callerPrincipal = (ApplicationPrincipal)result.members().nextElement();

        Assert.assertEquals(USERNAME_VALUE, callerPrincipal.getName());
        Assert.assertEquals(USERID_VALUE, callerPrincipal.getUserId());
        Assert.assertEquals(DISPLAYNAME_VALUE, callerPrincipal.getDisplayName());
    }

    //

    private void mockGroups(ResultSet rsMock, String[] roleNames) throws SQLException {
        BDDMyOngoingStubbing<Boolean> next = BDDMockito.given(rsMock.next());

        if(roleNames.length == 0) {
            next = next.willReturn(true);
        } else {
            for(@SuppressWarnings("unused") String roleName : roleNames) {
                next = next.willReturn(true);
            }
        }

        next.willReturn(false);

        //

        BDDMyOngoingStubbing<String> getString = BDDMockito.given(rsMock.getString("role"));

        if(roleNames.length == 0) {
            getString = getString.willReturn(null);
        } else {

            for(String roleName : roleNames) {
                getString = getString.willReturn(roleName);
            }
        }
    }

    private void assertGroups(Group result, String[] roleNames) {
        SimpleGroup groups = (SimpleGroup)result;

        // actual roles
        for(String roleName : roleNames) {
            Assert.assertTrue(groups.isMember(new SimplePrincipal(roleName)));
        }

        // number of roles
        Enumeration<Principal> roleEnumeration = groups.members();
        for(@SuppressWarnings("unused") String roleName : roleNames) {
            roleEnumeration.nextElement();
        }
        Assert.assertFalse(roleEnumeration.hasMoreElements());
    }
}
